浏览器功能定制3:使用 mitmproxy 增强浏览器的拦截能力
在《浏览器功能定制2:显示网页正文的阅读模式》中,我实现了网页的正文提取。于是继续着手于《浏览器功能定制1:将网页内容保存为 Markdown》,但是我遇到了新的挑战:如何将网页中的图片离线化。
在 Qt QWebEngine,你可以很方便地打开 devtools,查看网络请求、资源。同时,你也可以很方便地通过代码添加拦截器,拦截住网页的每一个请求。
但是,你没法通过代码,拦截到每一个请求的响应。(如果有方法,请告诉我)。
在查阅一番资料都没有找到答案后,我准备这么搞:
既然浏览器内部没有提供方便的数据拦截机制,那我就从外部找实现方案——mitmproxy。给浏览器加一层网络拦截层。
这是一个比较猛的方案,其功能远远不止图片离线化。更多强大功能,有待于慢慢发掘。在本文中,还是以图片离线化,作为切入场景。
mitmproxy 介绍
mitmproxy 是一个由 Python 开发的类似于 Charles 的网络请求调试工具。可以作为命令行工具运行。
但是,它还可以作为 Python 库,嵌入到程序中运行。我们正是利用了这个神奇特性。
需要注意,尽管是在程序内使用 mitmproxy,但操作系统还是要安装 mitmproxy 的证书。参见《mitmproxy 安装证书》。
多进程
由于 qutebrowser 运行在主进程上,我希望将 mitmproxy 运行在单独进程中,相互隔离。
具体代码如下:
import asyncio
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.options import Options as ProxyOptions
from mitmproxy.addons import TermLog, DumpAddon
from multiprocessing import Process
# 定义一个异步函数来运行服务器
async def run_proxy():
opts = ProxyOptions(listen_host='127.0.0.1', listen_port=8080)
m = DumpMaster(opts)
m.addons.add(TermLog())
m.addons.add(RequestLogger())
await m.run()
# 定义一个函数来启动服务器的事件循环
def start_proxy():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(run_proxy())
# 创建一个Process对象并启动它
proxy_process = Process(target=start_proxy)
proxy_process.start()
# 这里可以初始化和启动你的PyQt应用程序
# ...
# 在应用程序退出后确保代理服务器进程也关闭
proxy_process.join()
其中:
- server 运行在本地 8080 端口(需更换一个不常用)
- RequestLogger 这个 addon 用于保存请求数据
- 使用
multiprocessing
包来创建子进程 - 通过
proxy_process.join()
调用可以确保mitmproxy
进程在主进程退出之前已经正确地被清理。
拦截器
拦截器的代码实现如下:
class RequestLogger:
images_map = {}
def response(self, flow: http.HTTPFlow):
response = flow.response
if response == None:
return
if response.content == None:
return
headers = dict(response.headers)
content_type = headers.get("content-type", "")
# save images to images_map
if content_type.startswith("image/"):
path = urlparse(flow.request.url).path
filename = os.path.basename(path)
if filename:
print(f'save image: {filename}')
self.images_map[filename] = response.content
else:
print('error image url: {flow.request.url}')
这里给出了最核心的实现。
qutebrowser 中启动多线程
与 QApplication 一同启动时,需要注意技巧,如下代码示意:
if __name__ == '__main__':
# 初始化 PyQt 应用
app = QApplication(sys.argv)
# 在 PyQt 应用程序之前启动 mitmproxy 进程
proxy_process = Process(target=start_proxy)
proxy_process.start()
# 初始化和启动你的 PyQt 应用程序
window = YourMainWindow()
window.show()
# 启动事件循环
exit_code = app.exec_() # 注意:这里是 exec_() 而不是 exec(),这是 PyQt 的特性
# 等待代理进程结束
proxy_process.join()
sys.exit(exit_code)
qutebrowser 设置
可以通过如下命令设置:
:set content.proxy http://localhost:11270
小结
至此,qutebrowser 的所有流量都将经过 mitmproxy 传输。而我们可以通过代码控制 mitmproxy,从而掌控流量出入的每一个细节。
这带来了无数可能,令人激动。
本文作者:Maeiee
本文链接:浏览器功能定制3:使用 mitmproxy 增强浏览器的拦截能力
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!